﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using ComponentFactory.Krypton.Toolkit;
using StableDiffusionTools.Imaging;
using StableDiffusionTools.Progress;
using StableDiffusionTools.Utils;

namespace StableDiffusionTools.Controls
{
    public class HSLColorPicker : Control
    {
        public event EventHandler ValueChanged;
        protected void OnValueChanged()
        {
            ValueChanged?.Invoke(this, EventArgs.Empty);
        }

        [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
        public override Color BackColor { get => base.BackColor; set => base.BackColor = value; }

        private MouseState mouseState = MouseState.MouseOut;
        static int colorCount = 100;
        static int maxSatLumBitmapSize = 50;
        private float hueCircleSize = 0.2f;
        private Color[] hueCircleColors;
        UpdateThrottler updateThrottler = new UpdateThrottler(30);
        
        private double hue = 0f;
        public double Hue
        {
            get => hue;
            set
            {
                if(!MathUtil.NearEqual(hue, value))
                {
                    SetHue(value, true);
                }
            }
        }
        private double sat = 1f;
        public double Sat
        {
            get => sat;
            set
            {
                if (!MathUtil.NearEqual(sat, value))
                {
                    SetSat(value, true);
                }
            }
        }
        private double lum = 0.5f;
        public double Lum
        {
            get => lum;
            set
            {
                if (!MathUtil.NearEqual(lum, value))
                {
                    SetLum(value, true);
                }
            }
        }
        private double alpha = 1.0f;
        public double Alpha
        {
            get => alpha;
            set
            {
                if(!MathUtil.NearEqual(alpha, value))
                {
                    SetAlpha(value, true);
                }
            }
        }

        private IPalette _palette;

        //Constructor
        public HSLColorPicker()
        {
            SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw, true);
            BackColor = Color.FromKnownColor(KnownColor.Control);
            hueCircleColors = CreateColors(colorCount);

            _palette = KryptonManager.CurrentGlobalPalette;
            if (_palette != null)
                _palette.PalettePaint += new EventHandler<PaletteLayoutEventArgs>(OnPalettePaint);

            KryptonManager.GlobalPaletteChanged += new EventHandler(OnGlobalPaletteChanged);
            BackColor = _palette?.GetBackColor1(PaletteBackStyle.ControlClient, PaletteState.Normal) ?? BackColor;
        }

        private void OnPalettePaint(object sender, PaletteLayoutEventArgs e)
        {
            Invalidate();
        }

        private void OnGlobalPaletteChanged(object sender, EventArgs e)
        {
            if (_palette != null)
                _palette.PalettePaint -= new EventHandler<PaletteLayoutEventArgs>(OnPalettePaint);

            _palette = KryptonManager.CurrentGlobalPalette;

            BackColor = _palette?.GetBackColor1(PaletteBackStyle.ControlClient, PaletteState.Normal) ?? BackColor;

            if (_palette != null)
                _palette.PalettePaint += new EventHandler<PaletteLayoutEventArgs>(OnPalettePaint);

            Invalidate();
        }

        //Public Methods
        public void SetHSL(ColorHSL hsl)
        {
            //doNotBroadcastEvents = true;
            SetHue(hsl.H, false);
            SetSat(hsl.S, false);
            SetLum(hsl.L, false);
            SetAlpha(hsl.A, true);
            //doNotBroadcastEvents = false;
            OnValueChanged();
        }
        
        public ColorHSL GetHSL()
        {
            return new ColorHSL(hue, sat, lum, alpha);
        }

        //public Color GetColor()
        //{
        //    ColorHSL hsl = GetHSL();
        //    return hsl.ToColor();
        //}
        
        //Overrides
        protected override void OnPaint(PaintEventArgs pe)
        {
            base.OnPaint(pe);
            Graphics g = pe.Graphics;
            //g.PixelOffsetMode = PixelOffsetMode.HighQuality;
            g.InterpolationMode = InterpolationMode.Bilinear;
            //g.SmoothingMode = SmoothingMode.AntiAlias;
            g.Clear(BackColor);

            using (GraphicsPath path = new GraphicsPath(FillMode.Alternate))
            {
                PointF centre = new PointF(Width / 2f, Height / 2f);
                float radius = Math.Min(Width, Height) / 2f;
                float innerRadius = radius * (1.0f - hueCircleSize);
                //Draw Hue Circle
                path.AddLines(CreatePoints(colorCount + 1, centre, radius));
                path.AddLines(CreatePoints(colorCount + 1, centre, innerRadius));
                using (var brush = new PathGradientBrush(path))
                {
                    brush.CenterPoint = centre;
                    brush.CenterColor = Color.Black;
                    brush.SurroundColors = hueCircleColors;
                    brush.FocusScales = new PointF(0, 0);
                    g.FillPath(brush, path);
                }
                //Draw Hue Target
                float hueTargetRadius = innerRadius + ((radius - innerRadius) / 2f);//radius * .95f;
                float huePenSize = radius * .025f;
                float hueTargetSize = (radius - innerRadius) - huePenSize; //radius * .10f;
                float targetX = centre.X + hueTargetRadius * (float)Math.Cos(Hue * MathUtil.TwoPi);
                float targetY = centre.Y + hueTargetRadius * (float)Math.Sin(Hue * MathUtil.TwoPi);
                using (Pen p = new Pen(Color.Black, huePenSize))
                {
                    g.DrawEllipse(p, targetX - hueTargetSize * .5f, targetY - hueTargetSize * .5f, hueTargetSize, hueTargetSize);
                }
                //Draw SatLum Rectangle
                float rectRadius = innerRadius * 0.7f;
                int w = (int)(rectRadius * 2f);
                int h = (int)(rectRadius * 2f);
                int wLimit = Math.Min(maxSatLumBitmapSize, w);
                int hLimit = Math.Min(maxSatLumBitmapSize, h);
                if (h > 0 && w > 0)
                {
                    using (var bitmap = new Bitmap(wLimit, hLimit, PixelFormat.Format32bppPArgb))
                    using (FastBitmap f = bitmap.FastLock())
                    {
                        for (int y = 0; y < hLimit; y++)
                        {
                            for (int x = 0; x < wLimit; x++)
                            {
                                double currSat = (double)x / wLimit;
                                double currLum = 1.0 - ((double)y / hLimit);
                                f.SetPixel(x, y, (uint)ColorHSL.ToColor(Hue, currSat, currLum, 1.0f).ToArgb());//skip 2 calls by ToArgb() + cast to uint
                            }
                        }
                        f.Unlock();
                        using (ImageAttributes att = new ImageAttributes())
                        {
                            att.SetWrapMode(WrapMode.TileFlipXY);
                            g.DrawImage(bitmap, new Rectangle((int)(centre.X - rectRadius), (int)(centre.Y - rectRadius), w, h), 0, 0, wLimit, hLimit, GraphicsUnit.Pixel, att);
                        }
                    }
                    //Draw SatLum Target
                    float satLumTargetRadius = radius * .95f;
                    float satLumTargetSize = radius * .10f;
                    float satLumtargetX = (float)((centre.X - rectRadius) + (sat * w));
                    float satLumtargetY = (float)((centre.Y - rectRadius) + ((1.0f - lum) * h)); //inverse!
                    using (Pen p = new Pen(Color.Black, .2f * satLumTargetSize))
                    {//TODO: target seems slightly offset
                        g.DrawEllipse(p, satLumtargetX - satLumTargetSize * .5f, satLumtargetY - satLumTargetSize * .5f, satLumTargetSize, satLumTargetSize);
                    }
                }
            }
        }

        protected override void OnPaintBackground(PaintEventArgs e) { }

        protected override void OnMouseEnter(EventArgs e)
        {
            base.OnMouseEnter(e);
            mouseState = MouseState.MouseOver;
        }

        protected override void OnMouseLeave(EventArgs e)
        {
            base.OnMouseLeave(e);
            mouseState = MouseState.MouseOut;
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);
            if (updateThrottler.ReadyForUpdate)
            {
                if (e.Button == MouseButtons.Left)
                {
                    PointF origin = new PointF(Width / 2f, Height / 2f);
                    if (mouseState == MouseState.MouseHeldHue)
                    {

                        SetHue(ToHue(e.Location, origin), true);
                        OnValueChanged();
                    }
                    else if (mouseState == MouseState.MouseHeldSatLum)
                    {
                        SetSat(ToSat(e.Location.X, origin), false);
                        SetLum(ToLum(e.Location.Y, origin), true);
                        OnValueChanged();
                    }
                }
            }
        }

        protected override void OnMouseDown(MouseEventArgs e)
        {
            base.OnMouseDown(e);
            if (e.Button == MouseButtons.Left)
            {
                PointF origin = new PointF(Width / 2f, Height / 2f);
                if (IsWithinHueCircle(e.Location, origin))
                {
                    mouseState = MouseState.MouseHeldHue;
                    SetHue(ToHue(e.Location, origin), true);
                    OnValueChanged();
                }
                else if (IsWithinSatLumRectangle(e.Location, origin))
                {
                    mouseState = MouseState.MouseHeldSatLum;
                    SetSat(ToSat(e.Location.X, origin), false);
                    SetLum(ToLum(e.Location.Y, origin), true);
                    OnValueChanged();
                }
                else
                {
                    mouseState = MouseState.MouseOver;
                }
            }
        }

        //Private Methods
        private PointF[] CreatePoints(int count, PointF centre, float radius)
        {
            PointF[] result = new PointF[count];
            for (int i = 0; i < count; i++)
            {
                float maxX = centre.X + radius * (float)Math.Cos(((float)i / colorCount) * MathUtil.TwoPi);
                float maxY = centre.Y + radius * (float)Math.Sin(((float)i / colorCount) * MathUtil.TwoPi);
                result[i] = new PointF(maxX, maxY);
            }
            return result;
        }

        private Color[] CreateColors(int count)
        {
            Color[] result = new Color[count];
            for (int i = 0; i < colorCount; i++)
            {
                result[i] = ColorHSL.ToColor((double)i / colorCount, 1.0, 0.5, 1.0);
            }
            return result;
        }

        //Hue
        private bool IsWithinHueCircle(Point location, PointF origin)
        {
            bool result = false;
            var delta = new PointF(location.X - origin.X, location.Y - origin.Y);
            float distance = (float)Math.Sqrt(delta.X * delta.X + delta.Y * delta.Y);
            float radius = Math.Min(Width, Height) / 2f;
            float innerRadius = radius * (1.0f - hueCircleSize);
            if (distance > innerRadius && distance < radius)
                result = true;

            return result;
        }

        private double ToHue(Point location, PointF origin)
        {
            var delta = new PointF(location.X - origin.X, location.Y - origin.Y);

            float angleInRadians = (float)Math.Atan2(delta.Y, delta.X);
            angleInRadians = MathUtil.Wrap(angleInRadians, 0f, MathUtil.TwoPi);
            return angleInRadians / MathUtil.TwoPi;
        }
        //SatLum
        private bool IsWithinSatLumRectangle(Point location, PointF origin)
        {
            float radius = Math.Min(Width, Height) / 2f;
            float innerRadius = radius * (1.0f - hueCircleSize);
            float rectRadius = innerRadius * 0.7f;
            int w = (int)(rectRadius * 2f);
            int h = (int)(rectRadius * 2f);
            Rectangle rect = new Rectangle((int)(origin.X - rectRadius), (int)(origin.Y - rectRadius), w, h);
            return rect.Contains(location);
        }

        private double ToSat(int x, PointF origin)
        {
            float radius = Math.Min(Width, Height) / 2f;
            float innerRadius = radius * (1.0f - hueCircleSize);
            float rectRadius = innerRadius * 0.7f;
            int w = (int)(rectRadius * 2f);
            float delta = w - (rectRadius - (x - origin.X));
            return delta / w;
        }

        private double ToLum(int y, PointF origin)
        {
            float radius = Math.Min(Width, Height) / 2f;
            float innerRadius = radius * (1.0f - hueCircleSize);
            float rectRadius = innerRadius * 0.7f;
            int h = (int)(rectRadius * 2f);
            float delta = h - (rectRadius - (y - origin.Y));
            return 1.0 - (delta / h);
        }

        private void SetSat(double s, bool invalidate)
        {
            sat = MathUtil.Clamp(s, 0.0, 1.0);
            if (invalidate)
                Invalidate();
        }
        private void SetLum(double l, bool invalidate)
        {
            lum = MathUtil.Clamp(l, 0.0, 1.0);
            if (invalidate)
                Invalidate();
        }
        private void SetHue(double h, bool invalidate)
        {
            hue = MathUtil.Clamp(h, 0.0, 1.0);
            if (invalidate)
                Invalidate();
        }
        private void SetAlpha(double a, bool invalidate)
        {
            alpha = MathUtil.Clamp(a, 0.0, 1.0);
            if (invalidate)
                Invalidate();
        }

        enum MouseState
        {
            MouseOut,
            MouseOver,
            MouseHeldHue,
            MouseHeldSatLum,
        }

        private bool disposed = false;
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (!disposed)
                {
                    if (_palette != null)
                    {
                        _palette.PalettePaint -= new System.EventHandler<ComponentFactory.Krypton.Toolkit.PaletteLayoutEventArgs>(OnPalettePaint);
                        _palette = null;
                    }

                    ComponentFactory.Krypton.Toolkit.KryptonManager.GlobalPaletteChanged -= new System.EventHandler(OnGlobalPaletteChanged);
                    disposed = true;
                }

            }

            base.Dispose(disposing);
        }
    }
}
